/////////////////////////////////////////////////////////////////////////////
// Name:        ax_controller_simple.cpp
// Purpose:     A simple menu controller class.
// Author:      Julian Smart
// Modified by:
// Created:     2009-03-20
// RCS-ID:
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#include "wx/wx.h"
#include "wx/sysopt.h"

#include "ax/ax_view.h"
#include "ax/ax_controller_standard.h"

/*
 * AxMenuListCtrl
 */

#if axUSE_GENERIC_LISTCTRL
IMPLEMENT_CLASS( AxMenuListCtrl, wxGenericListCtrl )
#else
IMPLEMENT_CLASS( AxMenuListCtrl, wxListCtrl )
#endif

#if axUSE_GENERIC_LISTCTRL
BEGIN_EVENT_TABLE( AxMenuListCtrl, wxGenericListCtrl )
#else
BEGIN_EVENT_TABLE( AxMenuListCtrl, wxListCtrl )
#endif
    EVT_LIST_ITEM_SELECTED(wxID_ANY, AxMenuListCtrl::OnSelect)
    EVT_LIST_ITEM_ACTIVATED(wxID_ANY, AxMenuListCtrl::OnActivate)
    EVT_SIZE(AxMenuListCtrl::OnSize)

#ifdef __WXMAC__
    EVT_CHAR(AxMenuListCtrl::OnChar)
#endif
    
END_EVENT_TABLE()

AxMenuListCtrl::AxMenuListCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
{
    Init();

    // Native list control on Mac doesn't behave correctly (problems with programmatic selection of items).
#ifdef __WXMAC__
    //wxSystemOptions::SetOption(wxMAC_ALWAYS_USE_GENERIC_LISTCTRL, 1);
#endif

    Create(parent, id, pos, size, style);
}

AxMenuListCtrl::~AxMenuListCtrl()
{
}

bool AxMenuListCtrl::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
{
    AxListCtrlClass::Create(parent, id, pos, size, style);

    InsertColumn(0, wxT("Title"), wxLIST_FORMAT_LEFT, -1);

    return true;
}

void AxMenuListCtrl::Init()
{
    m_controller = NULL;
    m_menu = NULL;
    m_toActivate = -1;
}

void AxMenuListCtrl::OnSelect(wxListEvent& event)
{
    if (event.GetId() != GetId())
    {
        event.Skip();
        return;
    }

    if (m_controller && m_menu)
    {
        //wxPrintf(wxT("GetCurrentSelection(): %d\n"), GetSelection());
        m_menu->SetCurrentSelection(GetSelection());

        AxMenuItem* item = m_menu->GetItem(GetSelection());
        m_controller->Select(item);
    }
}

void AxMenuListCtrl::OnActivate(wxListEvent& event)
{
    if (event.GetId() != GetId())
    {
        event.Skip();
        return;
    }

    // Do in idle time (see below).
    m_toActivate = GetSelection();
}

// Needed for Mac only (native wxListCtrl)
void AxMenuListCtrl::OnChar(wxKeyEvent& event)
{
    if (event.GetKeyCode() == WXK_RETURN || event.GetKeyCode() == WXK_NUMPAD_ENTER)
    {
        wxListEvent event( wxEVT_COMMAND_LIST_ITEM_ACTIVATED, GetId() );
        event.SetEventObject( this );
        event.m_itemIndex = GetSelection();
        GetEventHandler()->ProcessEvent( event );
    }
    else
        event.Skip();
}

// Don't implement own OnIdle so we can sequence idle events more precisely
#if 0
void AxMenuListCtrl::OnIdle(wxIdleEvent& WXUNUSED(event))
{
    IdleProcessing();
}
#endif

// Perform any required idle processing
bool AxMenuListCtrl::IdleProcessing()
{
    // Try to work around message blocking when running a modal loop
    // inside Activate calls. On Windows, mouse clicks do nothing
    // when in a modal menu, though keyboard works for some reason.
    // Calling Activate in idle time and not from the OnActivate functions
    // seems to help.
    if (m_toActivate != -1 && m_controller && m_menu)
    {
        AxMenuItem* item = m_menu->GetItem(m_toActivate);
        m_toActivate = -1;
        if (item)
        {
            if (item->IsEnabled())
                m_controller->Activate(item);
        }
    }
    return true;
}


void AxMenuListCtrl::OnSize(wxSizeEvent& event)
{
    if (GetColumnCount() > 0)
    {
        SetColumnWidth(0, GetSize().x - 4);
    }
    event.Skip();
}

/// Apply settings to the appearance, in particular the background colour
bool AxMenuListCtrl::ApplySettings(int hints, const AxVisualSettings& settings)
{
    if (hints & AxVisualSettingWindowColour)
        SetBackgroundColour(settings.GetBestWindowColour());
    
    if (hints & AxVisualSettingTextColour)
        SetForegroundColour(settings.GetBestTextColour());

    return true;
}

// Called when the menu must be refreshed - implement for a specific control
bool AxMenuListCtrl::RefreshMenu(AxMenu* menu)
{
    DeleteAllItems();
    m_menu = menu;
    if (m_menu)
    {
        size_t i;
        for (i = 0; i < m_menu->GetCount(); i++)
        {
            AxMenuItem* item = m_menu->GetItem(i);

            wxListItem info;
            info.m_itemId = i;

            SetMenuItemData(info, item);
            long itemId = InsertItem(info);
            wxUnusedVar(itemId);
        }

        if (m_menu->GetCount() > 0)
        {
            int sel = 0;
            if (m_menu->GetCurrentSelection() >= 0 && m_menu->GetCurrentSelection() < (int) m_menu->GetCount())
                sel = m_menu->GetCurrentSelection();
            SetSelection(sel, true);
        }
    }
    return true;
}

// Called when a menu item must be updated
bool AxMenuListCtrl::UpdateMenuItem(AxMenu* menu, AxMenuItem* item)
{
    if (menu == m_menu)
    {
        int i;
        for (i = 0; i < (int) menu->GetCount(); i++)
        {
            if (menu->GetItem(i) == item)
            {
                wxListItem info;
                info.m_itemId = i;
                
                SetMenuItemData(info, item);
                SetItem(info);

                return true;
            }
        }
    }
    return false;
}

// Set the menu item data
bool AxMenuListCtrl::SetMenuItemData(wxListItem& info, AxMenuItem* item)
{
    // wxFont normalFont(m_controller->GetViewManager()->GetVisualSettings().GetMenuFont());
    wxFont normalFont(GetFont());
    if (!normalFont.IsOk())
        normalFont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); 
    normalFont.SetPointSize(m_controller->GetViewManager()->GetVisualSettings().GetBestTextSize());
    
    wxFont boldFont(wxFont(normalFont.GetPointSize(), normalFont.GetFamily(),
        normalFont.GetStyle(), normalFont.GetWeight()));

    wxFont italicFont(wxFont(normalFont.GetPointSize(), normalFont.GetFamily(),
        normalFont.GetStyle(), normalFont.GetWeight()));

    boldFont.SetWeight(wxFONTWEIGHT_BOLD);
    italicFont.SetStyle(wxFONTSTYLE_ITALIC);

    // wxColour enabledColour = m_controller->GetViewManager()->GetVisualSettings().GetMenuItemTextColour();
    wxColour enabledColour = m_controller->GetViewManager()->GetVisualSettings().GetBestTextColour();
    if (!enabledColour.IsOk())
        enabledColour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);

    wxColour selectedColour = m_controller->GetViewManager()->GetVisualSettings().GetMenuItemSelectedTextColour();
    if (!selectedColour.IsOk())
        selectedColour = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);

    wxColour disabledColour = m_controller->GetViewManager()->GetVisualSettings().GetMenuItemDisabledTextColour();
    if (!disabledColour.IsOk())
        disabledColour = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);

    wxColour normalBackgroundColour = m_controller->GetViewManager()->GetVisualSettings().GetMenuItemBackgroundColour();
    if (!normalBackgroundColour.IsOk())
        normalBackgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);

#if 0
    wxColour selectedBackgroundColour = m_controller->GetViewManager()->GetVisualSettings().GetMenuItemSelectedBackgroundColour();
    if (!selectedBackgroundColour.IsOk())
        selectedBackgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
#endif

    wxString label = item->GetLabel();

    info.m_text = label;
    info.m_mask = wxLIST_MASK_TEXT | /* wxLIST_MASK_IMAGE | */ wxLIST_MASK_DATA;
    // info.m_image = pmGetProjectManager().FindIconIndex(data.m_icon);
    info.m_data = (long) info.m_itemId;

    if (item->GetStyle() & AxMenuStyleBold)
        info.SetFont(boldFont);
    else if (item->GetStyle() & AxMenuStyleItalic)
        info.SetFont(italicFont);
    else
        info.SetFont(normalFont);
    
    if (!item->IsEnabled())
    {
        info.SetTextColour(disabledColour);

        // Problem: there's no SetSelectedItemBackgroundColour.
        // However, we could perhaps set the colour on selection.
        info.SetBackgroundColour(normalBackgroundColour);
    }
    else
        info.SetTextColour(enabledColour);
    
    return true;
}

// Called when a menu item should be selected - implement for a specific control
bool AxMenuListCtrl::SetMenuSelection(AxMenuItem* menuItem)
{
    if (m_menu)
    {
        size_t i;
        for (i = 0; i < m_menu->GetCount(); i++)
        {
            if (menuItem == m_menu->GetItem(i))
            {
                SetSelection(i, true);
            }
        }
    }
    return true;
}

// Called to get the currently selected menu item
AxMenuItem* AxMenuListCtrl::GetMenuSelection() const
{
    int sel = GetSelection();
    if (m_menu && sel != -1 && sel < (int) m_menu->GetCount())
    {
        return m_menu->GetItem(sel);
    }
    else
        return NULL;
}


// Find the selection
static long AxListCtrlGetSelection(AxMenuListCtrl& listCtrl)
{
    long item = listCtrl.GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
    return item;
#if 0
    long n = listCtrl.GetItemCount();
    long i;
    for (i = 0; i < n; i++)
    {
        if (listCtrl.GetItemState(i, wxLIST_STATE_SELECTED) & wxLIST_STATE_SELECTED)
        {
            return i;
        }
    }
    return -1;
#endif
}

// Select the given item
static void AxListCtrlSelectItem(AxMenuListCtrl& listCtrl, long sel, bool deselectOthers)
{
    long n = listCtrl.GetItemCount();
    long i;
    if (deselectOthers)
    {
        for (i = 0; i < n; i++)
        {
            if (listCtrl.GetItemState(i, wxLIST_STATE_SELECTED) & wxLIST_STATE_SELECTED)
            {
                listCtrl.SetItemState(i, 0, wxLIST_STATE_SELECTED);
            }
        }
    }
    if (sel != -1)
        listCtrl.SetItemState(sel, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
}

int AxMenuListCtrl::GetSelection() const
{
    return (int) AxListCtrlGetSelection(*(AxMenuListCtrl*)this);
}

void AxMenuListCtrl::SetSelection(long sel, bool select)
{
    if (!select)
        AxListCtrlSelectItem(*this, -1, true);
    else
        AxListCtrlSelectItem(*this, sel, true);
}

/*
 * AxMenuController implementation.
 * This controls how the menu is navigated, using
 * a supplied control. Derive from this class and
 * override functions that indicate when the menu
 * should change. Also, call functions in this object
 * to indicate activation, etc.
 */

IMPLEMENT_CLASS( AxStandardMenuController, AxMenuController )

AxStandardMenuController::~AxStandardMenuController()
{
}

void AxStandardMenuController::Init()
{
    m_viewManager = NULL;
}

// Called when the menu must be refreshed - implement for a specific control
bool AxStandardMenuController::RefreshMenu(AxMenu* menu, AxNavigationHint hint)
{
    if (m_listCtrl)
    {
        m_listCtrl->SetFocus();
        if (menu)
        {
            OnBeforeDisplay(menu);
        }
        m_listCtrl->RefreshMenu(menu);

        EmitSpeech(AxSpeechEventRefreshMenu, hint, AxSpeechFlagsPurge);
        
        return true;
    }
    else
        return false;
}

// Called when a menu item should be selected - implement for a specific control
bool AxStandardMenuController::SetMenuSelection(AxMenuItem* menuItem)
{
    if (m_listCtrl)
        return m_listCtrl->SetMenuSelection(menuItem);
    else
        return false;
}

// Called when we need to get the currently selected menu item - implement for a specific control
AxMenuItem* AxStandardMenuController::GetMenuSelection() const
{
    if (m_listCtrl)
        return m_listCtrl->GetMenuSelection();
    else
        return NULL;
}

// Called when the current view title should be changed - implement for a specific title control
bool AxStandardMenuController::SetTitle(const wxString& title)
{
    if (m_textCtrl)
        m_textCtrl->SetValue(title);
    return false;
}

// Called when the menu becomes modal
bool AxStandardMenuController::BeginModal()
{
    return GetViewManager()->MakeModal(true);
}

// Called when the menu stops being modal
bool AxStandardMenuController::EndModal()
{
    return GetViewManager()->MakeModal(false);
}

// Called when a menu item must be updated
bool AxStandardMenuController::UpdateMenuItem(AxMenuItem* item)
{
    if (m_listCtrl)
    {
        m_listCtrl->UpdateMenuItem(item->GetParent(), item);
        if (item == m_listCtrl->GetMenuSelection() && GetViewManager()->GetDescriptionCtrl())
            GetViewManager()->GetDescriptionCtrl()->SetValue(item->GetDescription());
        return true;
    }
    else
        return false;
}

/// Called when the menu should get the focus.
bool AxStandardMenuController::SetMenuFocus()
{
    if (m_listCtrl)
    {
        m_listCtrl->SetFocus();
        return true;
    }
    else
        return false;
}

/// Override to update the appearance when settings change.
bool AxStandardMenuController::UpdateAppearance(int hints)
{
    if (GetViewManager())
        m_listCtrl->ApplySettings(hints, GetViewManager()->GetVisualSettings());

    if (GetCurrentMenu())
        m_listCtrl->RefreshMenu(GetCurrentMenu());

    return true;
}

// Perform idle processing
bool AxStandardMenuController::IdleProcessing()
{
    // Perform the list control's idle processing first,
    // since item activation, selection etc. might override
    // further idle processing such as scheduled speech output.
    if (m_listCtrl)
        m_listCtrl->IdleProcessing();

    return AxMenuController::IdleProcessing();
}
